package util.graficos;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.annotations.XYDrawableAnnotation;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;

import util.Ponto;
import util.Util;
import util.jfreechart.CircleDrawer;
import util.output.Debug;

public abstract class Grafico {
	
	protected static final double ANOTACAO_PEQUENA = 5.0f;
	
	protected static final double ANOTACAO_GRANDE = 11.0f;

	private static final String PASTA = "img/" + Util.GRAFICOS_DIRETORIO + "/";
	
	private XYSeriesCollection seriesCollection;
	
	private JFreeChart chart;
	
	private XYSeries series;
	
	protected String nomeArquivo;
	
	protected String extensao;
	
	private int width;
	
	private int height;
	
	private String tituloPlot;
	
	private String tituloEixoX;
	
	private String tituloEixoY;
	
	private boolean mostraLegenda;
	
	private int keySeries;
	
	protected Ponto pontoFinalFaseTransiente;
	
	private ArrayList<XYAnnotation> listaAnotacoes;
	
	private ArrayList<XYPointerAnnotation> listaApontadoresAnotacoes;
	
	private Ponto pontoMinimo; // menor valor de X e menor valor de Y
	
	private Ponto pontoMaximo; // maior valor de X e maior valor de Y

	private double xPadding;

	private double yPadding;
	
	public Grafico() {
		this.keySeries = 1;
		this.series = new XYSeries(this.keySeries);
		this.seriesCollection = new XYSeriesCollection();
		
		this.width = Util.GRAFICOS_LARGURA;
		this.height = Util.GRAFICOS_ALTURA;
		
		this.tituloPlot = "";
		this.tituloEixoX = "";
		this.tituloEixoY = "";
		this.mostraLegenda = false;
		
		this.extensao = "png";
		this.nomeArquivo = Util.GRAFICOS_NOME;
		
		this.pontoFinalFaseTransiente = null;
		
		this.listaAnotacoes = new ArrayList<XYAnnotation>();
		this.listaApontadoresAnotacoes = new ArrayList<XYPointerAnnotation>();
		
		this.pontoMinimo = new Ponto(Double.MAX_VALUE, Double.MAX_VALUE);
		this.pontoMaximo = new Ponto(Double.MIN_VALUE, Double.MIN_VALUE);
		
		this.xPadding = 0;
		this.yPadding = 0;
	}

	public abstract void plot();
	
	public final void adicionaPonto(final double x, final double y) {
		series.add(x, y);
		
		if (x < this.pontoMinimo.getX()) {
			this.pontoMinimo.setX(x);
		}
		
		if (x > this.pontoMaximo.getX()) {
			this.pontoMaximo.setX(x);
		}
		
		if (y < this.pontoMinimo.getY()) {
			this.pontoMinimo.setY(y);
		}
		
		if (y > this.pontoMaximo.getY()) {
			this.pontoMaximo.setY(y);
		}
	}
	
	protected final void salvaImagem() {
		String filename = PASTA + this.nomeArquivo;
		
		if (this.series.getItemCount() > 0) {
			this.seriesCollection.addSeries(this.series);
		}
		
		configuraPlot();
		
		File file = new File(filename);
		try {
			ChartUtilities.saveChartAsPNG(file, this.chart, this.width, this.height);
		} catch (IOException e) {
			Debug.imprimeConsole("Erro ao salvar o gráfico " + filename);
		}
	}
	
	protected final void trocaRodada() {
		if (this.series.getItemCount() > 0) {
			this.seriesCollection.addSeries(this.series);
		}
		
		this.keySeries++;
		
		this.series = new XYSeries(this.keySeries);
	}
	
	private void configuraPlot() {
		this.chart = ChartFactory.createXYLineChart(this.tituloPlot, this.tituloEixoX, this.tituloEixoY, this.seriesCollection, PlotOrientation.VERTICAL, this.mostraLegenda, false, false);
		
		// Style the plot area
		this.chart.setBackgroundPaint(Color.WHITE);
		
		XYPlot plot = (XYPlot) this.chart.getPlot();
		plot.setBackgroundPaint(new Color(246, 246, 246));
		
		plot.setDomainGridlineStroke(new BasicStroke(0.5f));
		plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
		plot.setRangeGridlineStroke(new BasicStroke(0.5f));
		plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
		
		plot.getDomainAxis().setStandardTickUnits(NumberAxis.createIntegerTickUnits());
		
		double minX = this.pontoMinimo.getX() - this.xPadding;
		double minY = this.pontoMinimo.getY() - this.yPadding;
		
		plot.getDomainAxis().setRange(minX < 0 ? 0 : minX, this.pontoMaximo.getX() + this.xPadding);
		plot.getRangeAxis().setRange(minY < 0 ? 0 : minY, this.pontoMaximo.getY() + this.yPadding);
		
		if (this.pontoFinalFaseTransiente != null) {
			double x = this.pontoFinalFaseTransiente.getX();
			double y = this.pontoFinalFaseTransiente.getY();
			
			this.adicionaPontoAnotacao(x, y, ANOTACAO_GRANDE, "Final da fase transiente");
		}
		
		for (XYAnnotation anotacao : listaAnotacoes) {
			plot.addAnnotation(anotacao);
		}
		
		for (XYPointerAnnotation apontador : listaApontadoresAnotacoes) {
			plot.addAnnotation(apontador);
		}
	}
	
	protected final void adicionaPontoAnotacao(double x, double y, double size) {
		final CircleDrawer cd = new CircleDrawer(Color.green, new BasicStroke(1.0f), null);
		
		final XYAnnotation ponto = new XYDrawableAnnotation(x, y, size, size, cd);
		this.listaAnotacoes.add(ponto);
	}
	
	protected final void adicionaPontoAnotacao(double x, double y, double size, String anotacao) {
		this.adicionaPontoAnotacao(x, y, size);
		
		final XYPointerAnnotation apontador = new XYPointerAnnotation(anotacao, x, y, -Math.PI / 4.0);
		apontador.setBaseRadius(35.0);
		apontador.setTipRadius(5.0);
		apontador.setFont(new Font("Arial", Font.PLAIN, 12));
		apontador.setPaint(Color.blue);
		apontador.setTextAnchor(TextAnchor.HALF_ASCENT_LEFT);
		this.listaApontadoresAnotacoes.add(apontador);
	}
	
	protected void setTituloPlot(String titulo) {
		this.tituloPlot = titulo;
	}
	
	protected void setTituloEixoX(String titulo) {
		this.tituloEixoX = titulo;
	}
	
	protected void setTituloEixoY(String titulo) {
		this.tituloEixoY = titulo;
	}
	
	protected void setTituloCurvaAtual(String titulo) {
		this.series.setKey(titulo);
	}
	
	protected void setLegenda(boolean legenda) {
		this.mostraLegenda = legenda;
	}
	
	public void setPontoFinalFaseTransiente(Ponto pontoFinalFaseTransiente) {
		this.pontoFinalFaseTransiente = pontoFinalFaseTransiente;
	}

	public Ponto getPontoFinalFaseTransiente() {
		return pontoFinalFaseTransiente;
	}
	
	protected void setXPadding(double xPadding) {
		this.xPadding = xPadding;
	}

	protected void setYPadding(double yPadding) {
		this.yPadding = yPadding;
	}
}
